module Plot(createGraphScene,collagePos2GraphPos,screenPos2CollagePos,graphEndPos,graphStartPos) where 
import Mouse
import PlotData (Dim,Pos,Axis,AxisType,X_AXIS,Y_AXIS,PlotValue,GraphVal,Graph,
                 spaceXAxisValue,spaceYAxisValue,defaultAxis,graph,defaultAxisDecimalFun)

{-
   The length of the axis is based on dimension - borders.
   There are two borders, one for the rhs border and lhs border of the collage.

        <-------------------------------------------->
                         collSize
     (collTopX,collTopY)
       *-----------------------------------------------
       |   B4                                         |
       |  |\grphYEnd                                  |
       |  |                   *(0,0)                  |
       .  .                                           |
       .  . grphStart                       grphXEnd  |
       .B1|/_____________________X________________\ B2|
       |___B3_________________________________________|
                                                     (collX,collY)

  (collX,collY) - The rhs bottom of the collage
  (collTopX,collTopY) - The top of the collage
  B1 - Border between rhs X axis and collage rhs       
  B2 - Border between lhs X axis and collage lhs       
  B3 - Border between bottom Y axis and collage bottom
  B4 - border between top Y axis and collage top


  So to calculate the length of a x-axis
  sizeColl = collTopX - collX  , this gives us the actual x-size of the collage
  
  Then length of the x axis is found by:
  xAxisLen = sizeColl - B1 - B2


  Now that we know the axis length we can popuplate the
  axis with values. First we need the number of values
  which is going to populate the axis, we also need
  the axis length, each of the axis values gets a certain
  amount of length this is called spacing.
  e.g
  |---|---|---|---|---|
    ^   ^   ^
    |---|---|-  Spacing

  spacing is caluclated by taking the length of the axis and divide
  by the amount of values.
  spacing = xAxisLen / (len [vals] -1)
  the first is not used since its at the start


  

  Now to where to position the axis values.
  The graph start is located at, but now we need to
  use the collage position instead of the actual dimensions
  we have the dimension of the collage.
  The dimension is split in 2 with negative values on the lhs
  and positive on the rhs.
   B1                  B2
  |--|-------|--------|--|
-sz -g       0       +g  +sz
  <---------------------->
        sizeColl

   grph = collageSize / 2

  To get where the graph starts, this corresponds to both x and y start position on x-axis
  graphXstartPos = (-1 * sizeColl / 2) + B1

  On the y-axis
  graphYstartPos = (-1 * sizeColl / 2) + B3

  To group this toghether we use another function
 graphEndPos which calulates two positions, the first posistion
 is top left corner (tlc), and the second is the bottom right corner (brc)
 with these two position we can get the startposition by using brc.y and tlc.x
       
   |tlc
   |  
   |                    brc
   |--------------------

 Ok, so we have the start postition for both axis. Lets construct them

 to create the axis we need to create a path. Since path is a
 list of points (x,y) we first take the tlc to startPos and finally brc.
 Using the function createAxisPath.

 Now to populate the axis, we have to lists one for x and one for y
 the first value should be placed where the startPosition is, and then
 adding spacing until the end of the list

 This might sound too easy. 
 First I create a data type to specify which axis that we are working on
 X_AXIS | Y_AXIS
 We know that to calculate the position we just add spacing to the previous
 value, thus i created a function: calcPos which returns a Position
 when all values are inserted. But instead of applying all values
 I just apply "AxisType -> Float" which correponds to the axis type
 described before and the spacing (which is calculated by spacing function)
 for that axis. This will return me a new function of type
 "(Pos a ->  Pos a)" I can now apply the previus position when i have that
 and I will get a new position. By using this "not completed function"
 and send it in as an argument to a recursive function "axisValues"
 I don't need to specify which axis im using, that i've already done in caclPos.
 
 Unfortunatly if I send in the first value, I will have the first value located
 at the 1st position and not in startPos as I want, so i can either create a new
 formObj by using createValPt with the startPosition (which i've done) or
 i can substract the startPos with spacing before sending it to axisValue.
 I choose the first.

 One thing that i haven't yet have a good solution for is the space
 between the axis and the axis values. E.g:


  |
  |
  |____________   
                  |- This space, and the space between y and values there
  0  1  2  3  4 


 For now it is set with the
 spaceXAxisValue - for the value between x axis and its values
 spaceYAxisValue - for the value between y axis and its values


 Next section will handle the values for which the graph
 is popuplated.
 each value is represented by a position and a description.
 E.g ((px=1.2,py=2.5),desc="String")
 The X position will be located startPos.px + 1.2 * space for x
 and Y position will be located startPos.px + 2.5 * space for y
 the description should be located X-pos +/- descPosX, Y-Pos +/- descPosY

 That gives us a new Record types

 PlotValue = {{Pos},"String"}
 and
 GraphValues = [PlotValue]

 We also need to determine some attribute for the
 graph itself.

 GraphValueAttribute a = attachingLines
                         LineStyle
                         PointStyle

It is now possible to add GraphAttribute to GraphValues.

The next part is to actually draw a line between the graph points.
Since points on the graph is defined by the type PlotValue which
consists of {Pos , Description.....} where Posistion is px,py and
Description is a string. Since we move the point for each value
we can easily take these points and create a path which then can
be used to create a tracedPath, voilá! We have our lines between the
values. Therefor we also add a new form to each PlotValue which
describes the point (Default black circle) and a linestyle


CrossHair:
The feature of a crosshair is to put a line between the axis to the point where
the mouse is positioned. We need to somehow convert the position of the mouse
to the position in the collage.
         (xLhs,yThs)                            
             |                                   
             |                                   
             |                                   
  (xLhs,pty) +--------*(ptx,pty)                 
             |        |                          
             ---------+-------------- (xRhs,yBhs)
                   (ptx,yBhs)

 The above is the logical structure, what we are missing is the conversion of the
 mouse points (ptx,pty) to collage position.
          (tlcX,tlcY)  
 collage   |------------------------|
           |                        |
           |           X  *         |   X = (0,0) collage point
           |           (mptx,mpty)  |
           |------------------------|(brcX,brcY) 

 In the above picture we have a mouse position (mptx,mpty),
 so to caclulate the point where the mouse is in the collage we do

 To easisly understand how the calculation is done
 let think of this
   |   d
   +--------* mptx
   |
 xBorder

  xBorder = -(brcX-tlcX/2)

  The furthest point (xBorder) in the collage is located at
  -(brcX-tlcX)/2) that is the collage point for xborder,
   this is where "+" is currently located on the picture above.
  now we just need to add the distance to mptX, which should be
   mptx. This gives us the collage point:

  -((tlcX)/2) + (mptX - border spaces)
  we also need to remove the border space between the actual collage and
  the border which means. tlcX is the space between the collage and the screen,
  and b1 is the space between collage and the axis.
  Xpos = -(brcX - tlc.px)/2 + (pos.px - tlcX - b1)
  

  On the otherside we have the y axis.
  We have the position

  |        *
  |        | d
  ---------+---------  yBorder

 The furthest point on yBorder in the collage is located at
 yBorder = -((tlcY-brcY)/2)  . Thus we need to find d to 
 add the distance to yBorder. Unfortunatly it doesn't work
 exactly like the xBorder case since the mouse point y is
 increasing as it goes down further towards yBorder.
 But if we switch the whole thing around, and calculate the
 distance from the top, then we remove the total distans,
 to get the distance from the bottom.

 (tlc.x,tlc.y)
  ----------------  y1Border
  |       | d1
  |       * p1
  |
  |       x(0,0)
(tlcX,tlcY) 

 yBorder = -((tlcY-brcY)/2)
 dyTot = brc.y - tlc.y - axis.b3 
 d1 = pos.py - tlc.y
 thus
 d=  (-(brc.y - tlc.y)/2) + axis.b3 + (dyTot - (pos.py - tlc.y) )

 
 Now that we have the crossHair implemented we maybe want
 values to see where we are.  There is one function called
 collagePos2GraphPos which creates a position in the graph,
 this might be fine if we have 1,2,3,4,5. But in the case
 where we have something else we need to find a better solution.
 The best way I can think of is to round the graph position and
 find that in the list of that axis..E.g


 |                  
 -------------------
      10    20    30 .....

 if we now have the position 1.2356 we can floor it of to 1
 then get the 1st out of the x-axis list which will be 10 in this case.

 This is all fine, but sometimes you don't just want the value you also want
 the decimals, e.g 10.2 or Feb 20th..
 10.2 might be easy, though 20th is abit harder, therefor the xAxisDecimal is
 introduced.

   |
   |
   |_________________
      FEB MAR APRIL

 In this case the value inbetween e.g Feb mar is the number of days.
 Therefor instead of setting values we define a function as follows

 In: Axis in: GraphPos Out: Form - to be placed in the graph. For example a text
 ------------
 Axis -> Pos -> (Element,Element)

 In case of using the 1 2 , we know there are 10 decimals. But there are other
 for example months that have different amount of days for different months.
        
-}                                                     



-- Extra functions ---




getNth: [String] -> Int -> Maybe String
getNth strs nr = case strs of 
                  st::rest -> if | nr == 0 -> Just st
                                 | otherwise -> getNth rest (nr-1)
                  [] -> Nothing
                  

findValueNth: Axis -> (Int,Int) -> (String,String)
findValueNth (Axis axis) (xNth,yNth) = let strX = case (getNth axis.xAxis xNth) of
                                                   Just x -> x
                                                   Nothing -> "n/a"
                                           strY = case (getNth axis.yAxis yNth) of
                                                   Just y -> y
                                                   Nothing -> "n/a"
                                       in (strX,strY)


getAxisStrValueFromCollagePos: Axis -> Pos -> (String,String)
getAxisStrValueFromCollagePos (Axis axis) pos = let grPos = collagePos2GraphPos (Axis axis) pos
                                                    nthXPos = axis.crossHairValueSpaceFun grPos.px
                                                    nthYPos = axis.crossHairValueSpaceFun grPos.py
                                                    str = findValueNth (Axis axis) (nthXPos,nthYPos)
                                                in str
                                            
--------------- Collage functions ----------------
--CollageSize - returns the absolut collage size
collageSize : Axis -> (Float,Float)
collageSize (Axis axis) = let x = axis.brc.x - axis.tlc.x
                              y = axis.brc.y - axis.tlc.y
                          in (x,y)


--------------------------------------------------
--------------- Graph Functions ------------------
graphEndPos : Axis -> (Pos, Pos )
graphEndPos (Axis axis) = let (x,y) = collageSize (Axis axis)
                              xLhs = -1*x/2 + axis.b1
                              xRhs = x/2 - axis.b2
                              yBhs = -y/2 + axis.b3
                              yThs = y/2 - axis.b4
                          in ({px=xLhs,py=yThs},{px=xRhs,py=yBhs})

axisLen : Axis -> Dim
axisLen (Axis axis) = let (x,y) = collageSize (Axis axis)
                          xLen = x - axis.b1 - axis.b2
                          yLen = y - axis.b3 - axis.b4
                      in {x=xLen,y=yLen}


valueSpace : Axis -> (Float,Float)
valueSpace (Axis axis) = let {x,y} = axisLen (Axis axis)
                             xGraphSpace =  x / toFloat ((length axis.xAxis)-1)
                             yGraphSpace =  y / toFloat ((length axis.yAxis)-1)
                         in (xGraphSpace,yGraphSpace)

graphStartPos : Axis -> Pos
graphStartPos (Axis axis) = let (tlc,brc) = graphEndPos (Axis axis)
                            in {px=tlc.px,py=brc.py}
                        
--(tlc,brc) = graphEndPos (Axis axis)
screenPos2CollagePos : Axis -> Pos -> Pos
screenPos2CollagePos (Axis axis) pos = let brc = axis.brc
                                           tlc = axis.tlc
                                           dyTot = brc.y - tlc.y - axis.b3 
                                           xPos = -(brc.x - tlc.x)/2 + (pos.px - axis.tlc.x)
                                           yPos = (-(brc.y - tlc.y)/2) + axis.b3 + (dyTot - (pos.py - tlc.y) )  
                                       in {px=xPos,py=yPos}
                                          
                                          

--Somehow this is working, but according to be the tlc and brc is switched??
collagePos2GraphPos : Axis -> Pos -> Pos
collagePos2GraphPos (Axis axis) pos = let (spX,spY) = valueSpace (Axis axis)
                                          (tlc,brc) = graphEndPos (Axis axis)
                                          xVal = (brc.px + pos.px) / spX
                                          yVal = (tlc.py + pos.py) / spY
                                      in {px=xVal,py=yVal}


---------------Creating visuals for graphs -------

createAxisPath : Axis -> Path
createAxisPath (Axis axis) = let startPos = graphStartPos (Axis axis)
                                 (tlc,brc) = graphEndPos (Axis axis)
                             in [ (tlc.px,tlc.py),(startPos.px,startPos.py),(brc.px,brc.py) ]

createAxisForm : Axis -> Form
createAxisForm (Axis axis) = traced axis.axisLines <| createAxisPath (Axis axis)





-------------Axis value points -------------------
createValPt : String -> Pos -> Form
createValPt str pos = let ele = plainText str
                          form = toForm ele
                      in move (pos.px,pos.py) form



axisXValues : [String] -> Float -> Pos -> [Form]  
axisXValues strs space pos  = case strs of 
                                   str::rest -> let xPos = pos.px + space
                                                    yPos = pos.py
                                                    formObj = createValPt str {px=xPos,py=yPos}
                                                in  formObj :: axisXValues rest space {px=xPos,py=yPos}
                                   [] -> []


axisValues : [String] -> (Pos ->  Pos) -> Pos -> [Form]
axisValues strs spaceFun pos = case strs of 
                                    str::rest -> let mvPos = spaceFun pos
                                                     formObj = createValPt str mvPos
                                                 in formObj :: axisValues rest spaceFun mvPos
                                    [] -> []







calcPos : AxisType -> Float -> Pos ->  Pos
calcPos aType space pos = case aType of
                               X_AXIS -> {px = pos.px+space, py=pos.py}
                               Y_AXIS -> {px = pos.px, py=pos.py+space}



populateAxis : Axis -> Form
populateAxis (Axis axis) = let (xSp,ySp) = valueSpace (Axis axis)
                               calcXFun = calcPos X_AXIS xSp
                               calcYFun = calcPos Y_AXIS ySp
                               startPos = graphStartPos (Axis axis)

                               lstXObj = createValPt (head axis.xAxis) {px=startPos.px,py=startPos.py-spaceXAxisValue}
                               xVals = drop 1 axis.xAxis
                               xForms = lstXObj::axisValues xVals calcXFun {px=startPos.px,py=startPos.py-spaceXAxisValue}
                               lstYObj = createValPt (head axis.yAxis) {px=startPos.px-spaceYAxisValue,py=startPos.py}
                               yVals = drop 1 axis.yAxis
                               yForms = lstYObj::axisValues yVals calcYFun {px=startPos.px-spaceYAxisValue,py=startPos.py}
                           in group (xForms++yForms)
                        
--------------------- Axis Values ----------------
--                 start    position   spacing
getGraphPosition : Pos -> Pos -> (Float,Float) -> (Float,Float)
getGraphPosition stPos plotPos (spX,spY) = let xPos = stPos.px + plotPos.px * spX
                                               yPos = stPos.py + plotPos.py * spY
                                           in (xPos,yPos)


placeDescAbovePoint : String -> Pos -> Form
placeDescAbovePoint descr pos =  move (pos.px,pos.py+10) <| toForm <| plainText descr


placeValueInGraph : Axis -> PlotValue -> (Pos,Form)
placeValueInGraph (Axis axis) pltVal = let stPos = graphStartPos (Axis axis)
                                           (spX,spY) = valueSpace (Axis axis)
                                           (xPos,yPos) = getGraphPosition stPos pltVal.pos (spX,spY)
                                           ptForm = if | pltVal.showDesc -> group [ move (xPos,yPos) <| pltVal.valuePt,
                                                                                    placeDescAbovePoint pltVal.desc {px=xPos,py=yPos}]
                                                       | otherwise -> move (xPos,yPos) <| pltVal.valuePt
                                       in ({px=xPos,py=yPos}, ptForm)
                                    


graphValues : Axis -> [PlotValue] -> [(Pos,Form)]
graphValues axis vls = case vls of 
                         val::rest -> let (pos,valForm) = placeValueInGraph axis val
                                      in (pos,valForm) :: graphValues axis rest
                         [] -> []

lineBtwValues : [Pos] -> Path
lineBtwValues posArr = case posArr of
                        pos::rest -> (pos.px,pos.py):: lineBtwValues rest
                        [] -> []


graphValueForm : Axis -> [PlotValue] -> LineStyle -> Form
graphValueForm (Axis axis) vls lineStyle = let (poses,forms) = unzip (graphValues (Axis axis) vls)
                                               linePath = traced lineStyle <| lineBtwValues poses
                                           in group (linePath :: forms)
                                       

addPlotValues: Axis -> Graph -> [Form]
addPlotValues (Axis axis) ({graphVals} as grph) = case graphVals of
                                                   grVal::rest -> let frm = graphValueForm (Axis axis) grVal.plotValues grVal.lineStyle
                                                                  in frm::addPlotValues (Axis axis) {graphVals=rest}
                                                   [] -> []

getPlotValueForm: Axis -> Graph -> Form
getPlotValueForm (Axis axis) grph = group <| addPlotValues (Axis axis) grph

--------------------------------------------------
------------------Cross hair ---------------------

createCrossHairLine : Axis -> Pos -> Form
createCrossHairLine (Axis axis) pos = let  (tlc,brc) = graphEndPos (Axis axis) --Fishy
                                           yLine = traced axis.crossHairLines [(tlc.px,pos.py),(pos.px,pos.py)]
                                           xLine = traced axis.crossHairLines [(pos.px,brc.py),(pos.px,pos.py)]
                                      in group [yLine,xLine]
                                  
addCrossHairValues : Axis -> Pos -> Form
addCrossHairValues (Axis axis) collPos = let valPos = getAxisStrValueFromCollagePos (Axis axis) collPos
                                             frm = createValPt (show valPos) collPos
                                         in frm
                                     
addCrossHairDecValue : Axis -> Pos -> Form
addCrossHairDecValue (Axis axis) pos = let grPos = collagePos2GraphPos (Axis axis) pos
                                           (xDec,yDec) = axis.axisDecimalFun (Axis axis) grPos 
                                           (tlc,brc) = graphEndPos (Axis axis)
                                           xFrm = move (pos.px,brc.py) <| toForm xDec
                                           yFrm = move (tlc.px,pos.py) <| toForm yDec
                                       in group [xFrm,yFrm]
                                    

--------------------------------------------------
--  Axis -> CollagePosition -> 
--------------------
constructAxis : Axis -> Pos -> Form
constructAxis (Axis axis) pos = if | axis.crossHair -> group [ createAxisForm (Axis axis) , 
                                                               createCrossHairLine (Axis axis) pos,
                                                               addCrossHairValues (Axis axis) pos,
                                                               addCrossHairDecValue (Axis axis) pos
                                                             ]
                                   | otherwise -> createAxisForm (Axis axis)


createGraphScene : Axis -> Graph -> Pos -> Element
createGraphScene (Axis axis) graph pos= let collPos = screenPos2CollagePos (Axis axis) pos
                                            axisForm = constructAxis (Axis axis) collPos
                                        in collage (truncate (axis.brc.x-axis.tlc.x)) (truncate (axis.brc.y-axis.tlc.y))  
                                           [ 
                                             axisForm,
                                             populateAxis (Axis axis),
                                             getPlotValueForm (Axis axis) graph
                                           ]
                         


showOut (x,y) = let pos = {px= (toFloat x),py=(toFloat y)}
                    
                    coll = createGraphScene defaultAxis graph pos
                    collPos = screenPos2CollagePos defaultAxis pos
                    grPos = collagePos2GraphPos defaultAxis collPos 
                    (xDec,yDec) = defaultAxisDecimalFun defaultAxis grPos
                in flow down [coll,
                              asText (x,y),
--                              asText defaultAxis,
                              asText (collageSize defaultAxis),
                              asText (axisLen defaultAxis),
                              plainText "ValueSpace",
                              asText (valueSpace defaultAxis),
--                              asText (graphStartPos defaultAxis),
--                              asText (graphEndPos defaultAxis),
--                              asText (graphValueTest),
--                              asText (screenPos2CollagePos defaultAxis pos),
                              asText (collagePos2GraphPos defaultAxis 
                                      <| screenPos2CollagePos defaultAxis pos),
                              plainText "Find right Val",
                              asText (getAxisStrValueFromCollagePos defaultAxis 
                                      <| screenPos2CollagePos defaultAxis pos),
                              plainText "X decimal,Y decimal",
                              flow right [plainText "x=",xDec,plainText "y=",yDec]
                    
                             ]


--main = lift showOut Mouse.position



 
